use core::time::Duration;

use crate::{
    process::{
        process::{Process, ProcessId, PROCESSES},
        timer::TIMER,
    },
    syscall::SysError,
};

use super::{check_and_clone_cstr, check_and_clone_cstr_array, SysResult, Syscall};

impl Syscall<'_> {
    pub fn sys_yield(&mut self) -> SysResult {
        crate::process::r#yield();
        Ok(0)
    }

    pub fn sys_sleep(&mut self, duration: u64) -> SysResult {
        let thread = self.thread.upgrade().unwrap();

        TIMER.lock().add(Duration::from_millis(duration));
        thread.write().sleeping = true;

        self.sys_yield()
    }

    pub fn sys_getpid(&mut self) -> SysResult {
        Ok(self.process().upgrade().unwrap().read().id.0 as usize)
    }

    pub fn sys_gettid(&mut self) -> SysResult {
        Ok(self.thread.upgrade().unwrap().read().id.0 as usize)
    }

    pub fn sys_exec(
        &mut self,
        path: *const u8,
        argv: *const *const u8,
        envp: *const *const u8,
    ) -> SysResult {
        let path = check_and_clone_cstr(path)?;
        let args = check_and_clone_cstr_array(argv)?;
        let envs = check_and_clone_cstr_array(envp)?;

        if args.is_empty() {
            error!("exec: args is null");
            return Err(SysError::EINVAL);
        }

        info!("exec: path: {:?}, args: {:?}, envs: {:?}", path, args, envs);

        let id = Process::new_user_process("exec", path.as_str(), argv as usize, envp as usize, 0)
            .ok_or(SysError::EBADF)?
            .read()
            .id;

        info!("exec successful: pid = {}", id.0);

        Ok(id.0 as usize)
    }

    pub fn sys_exit(&mut self, exit_code: usize) -> SysResult {
        let tid = self.thread.upgrade().unwrap().read().id;
        info!("exit: {}, code: {}", tid.0, exit_code);

        let proc = self.process().upgrade().unwrap();
        proc.write()
            .threads
            .retain(|thread| thread.read().id != tid);

        if proc.read().threads.len() == 0 {
            proc.read().exit_process();
        }

        drop(proc);
        self.exit = true;
        Ok(0)
    }

    pub fn sys_waitpid(&mut self, pid: usize) -> SysResult {
        loop {
            if PROCESSES
                .read()
                .iter()
                .find(|process| process.read().id == ProcessId::from(pid as u64))
                .is_none()
            {
                break;
            }
            x86_64::instructions::interrupts::enable_and_hlt();
        }
        Ok(pid)
    }
}
